/*
	jGrouse framework
	Copyright 2007 by Denis Riabtchik 
	All rights reserved.
	
	Licensed under modified BSD license. See license.txt for details
*/

/**
	Structure describing a class
	@interface ClassDef
*/
/**
	Name of class
	@variable className
*/

/**
	Name of superclass, if applicable
	@variable superclassName
*/

/**
	Function returning members of class
	@variable properties 
*/


/**@scope jgrouse */

/**
	Factory function for XMLHttpRequest objects
	@function getTransport
*/
jgrouse.getTransport = function()
{
	jgrouse.notImplemented('jgrouse.getTransport');
	// see implementation below
};

(function()
{
	var standardTransport = function()
	{
		return new XMLHttpRequest();
	}
	
	var ids = ['Msxml2.XMLHTTP.4.0', 'Msxml2.XMLHTTP', 'Microsoft.XMLHTTP'];
	var found = null;
	
	
	var msTransport = function()
	{
		if (found)
		{
			return new ActiveXObject(found);
		}
		else
		{
			for (var i = ids.length - 1; i >= 0; i--)
			{
				try
				{
					var v = new ActiveXObject(ids[i]);
					found = ids[i];
					return v;
				}
				catch (e)
				{
				}
			}
			if (found == null)
			{
				throw 'XMLHttpRequest is not available';
			}	
		}
	}
	
	jgrouse.getTransport = (jgrouse._global.XMLHttpRequest)? standardTransport : msTransport;
})()




/**
	Standard constructor used by JGrouse framework.
	Not to be used directly
	@function {private} jgrouse.standardConstructor
*/
jgrouse.standardConstructor = function()
{
	if (!this._prototyping)
	{
		this.initialize.apply(this, arguments);
	}
	return this;
}

/**
	Declare class for jGrouse framework
	Not to be used directly
	@function {private} create
*/
jgrouse.create = function(className)
{
	var name = className.split('.');
	var current = jgrouse._global;
	for (var i = 0; i < name.length - 1; i++)
	{
		var part = name[i];
		if (!current[part])
		{
			current[part] = {};
		}
		current = current[part];
	}
	var lastPart = name[name.length - 1];
	current[lastPart] = function() 
	{ 
		return jgrouse.standardConstructor.apply(this, arguments);
	};
	return current[lastPart];
}

/**
	Define class. If class is defined inside a module, actual definition would be deferred until all classes 
	in the module are declared.<br>
	Example 1:
		<div class="example">
jgrouse.define('mySubclass', 'mySuperclass', function(sugar)
{
	eval(sugar);

	function myPrivateMember(arg)
	{
		// do some magic
	}


	return { // note that the curly bracket should be 
		   // on the same line as return keyword
		initialize : function(arg1, arg2)
		{
			_super(this, 'initialize', arguments)
		},
		methodOne : function(arg1, arg2)
		{
			var privateResult = myPrivateMember.call(this, arg2);
			return _super(this, 'methodOne', [arg1]) + privateResult;
		}
	}
});	
		</div>
	Example 2:
		<div class="example">

jgrouse.define({
	subclassName : 'mySubclass',
	superclassName : 'mySuperclass',
	properties : function(sugar)
	{
		eval(sugar);
		
		return {
			....
		}
	}
});
		</div>
		@function define
		@paramset Option 1
		@param {String} subclassName - name of new class 
		@param {String} superclassName - name of super class for the new class. If super class does not exist, pass null
		@param {function(embed)} body - function that returns structure with members of class. 
		@paramset Option 2
		@param {Object} obj - structure containing object's parameters:
        @... {String} subclassName - name of new class 
        @... {String} superclassName - name of super class for the new class. If super class does not exist, pass null
        @...  {function(embed)} body - function that returns structure with members of class
*/
jgrouse.define = function( subclassName, superclassName, properties)
{
	if (arguments.length == 1 && typeof subclassName == 'object')
	{
		var v = subclassName;
		subclassName = v.subclassName;
		superclassName = v.superclassName;
		properties = v.properties;
	}
	jgrouse.ClassLoader.instance.define(subclassName, superclassName, properties);
}

/**
	Define module.
	Module is a container of classes and utility functions that are related to each other. 
Example 1:
		<div class='example'>
jgrouse.module(
{
	moduleName : 'myModule',
	requires : ['moduleOne', 'moduleTwo'],
	imports : ['com.foo.Bar', 'com.foo.Brick'],
	body : function(sugar)
	{
		eval(sugar);
		// module body follows
	},
	postInit : function(sugar)
	{
		eval(sugar);
		...
	}
});
		</div>	
	Example 2:
		<div class='example'>
jgrouse.module(
	'myModule',
	['moduleOne', 'moduleTwo'],
	['com.foo.Bar', 'com.foo.Brick'],
	function(sugar)
	{
		eval(sugar);
		// module body follows
	},
	function(sugar)
	{
		eval(sugar);
		// post init code
		...
	}
);
		</div>		
	@function module
	@paramset Option 1
	@param {String} name - name of the module
	@param {String[]} requires - array of strings containing names of modules that are used by this module, optional
	@param {String[]} imports - array of strings containing names of classes that should be imported into module's namespace
	@param {function(embed)} body - function containing body of module. Called when all the required modules are loaded
	@param {function(embed)} postInit - function that is called after module is initialized. Called after initialization of module (optional)
	@paramset Option 2
	@param {Object} obj - structure containing the module parameters
    @... {String} name - name of the module
    @... {String[]} requires - array of strings containing names of modules that are used by this module, optional
    @... {String[]} imports - array of strings containing names of classes that should be imported into module's namespace
    @... {function(embed)} body - function containing body of module. Called when all the required modules are loaded
    @... {function(embed)} postInit - function that is called after module is initialized. Called after initialization of module (optional)
	
*/
jgrouse.module = function(name, requires, imports, body, postInit)
{
	if (arguments.length == 1 && typeof name == 'object')
	{
		var v = name;
		name = v.name;
		requires = v.requires;
		body = v.body;
		imports = v.imports;
		postInit = v.postInit;
	}
	if (jgrouse.ClassLoader.instance._deferLoading)
	{
		jgrouse.ClassLoader.instance.addModule(name, requires, imports, body, postInit);
	}
	else
	{
		window.setTimeout(function()
		{
			jgrouse.ClassLoader.instance.addModule(name, requires, imports, body, postInit);
		}, 1);		
	}
}

/**
	Loads one or several modules and invokes callback once module is loaded and initialized
	@function require
	@paramset Load one module
	@param {String} module - name of module to be loaded
	@param {function()} callback - function to be invoked after module is initialized
	@paramset Load several modules
	@param {String[]} modules - array with names of modules to be loaded
	@param {function()} callback - function to be invoked after module is initialized
	
*/
jgrouse.require = function(module, callback)
{
	var arr = null;
	if (typeof module == 'string')
	{
		arr = [module];
	}
	else
	{
		arr = module;
	}
	function checker()
	{
		for (var i = 0; i < arr.length; i++)
		{
			if (!jgrouse.ClassLoader.instance.isLoaded(arr[i]))
			{
				return;
			}
		}
		callback();
	}
	for (var i = 0; i < arr.length; i++)
	{
		jgrouse.ClassLoader.instance.requireModule(arr[i], checker);
	}
}

/**
	Returns object corresponding to given name
	@function resolveName
	@param {String} name - name of object to be resolved
	@returns either found object or null if it was not found
*/
jgrouse.resolveName = function(name)
{
	var arr = name.split('.');
	var current = jgrouse._global;
	for (var i = 0; i < arr.length; i++)
	{
		var part = arr[i];
		if (current[part])
		{
			current = current[part];
		}
		else return null;
	}
	return current;
}

/**
 * Converts given URL to full one. If URL is relative, then it is converted to absolute, using 
 * current window's location
 * @function {String} fullUrl
 * @param {String} url - url to convert
 * @return absolute url
 */
jgrouse.fullUrl = function(url)
{
	var d = url.split('?')[0].split('#')[0];
	if (d.indexOf('://') > 0)
	{
		return url; // url represents ref to another host
	}
	if (url.charAt(0) == '/')
	{
		return window.location.protocol + '//' + window.location.hostname + url;
	}
	d = window.location.href.split('?')[0].split('#')[0];
	if (d.charAt(d.length - 1) != '/')
	{
		// need to pop the last part
		var v = d.split('/');
		v.pop();
		d = v.join('/') + '/';
	}
	return d + url;
}

/**
	Get a string that could be eval'ed in order to bring into current namespace properties of given object.
	If the argument passed is in the form of
	<div class="example">
		foo.bar.Bla
    </div>
	then the resulting string would be
	<div class="example">
		var Bla = foo.bar.Bla
    </div>
	If the last part of argument is *, then all properties that are found are imported. 
	For example, if com.bar.util has properties propOne and propTwo, then
	<div class="example">
		jgrouse.getImportString('com.bar.util.*')
	</div>
	would result in
	<div class="example">
		var propOne = com.bar.util.propOne; var propTwo = com.bar.util.propTwo
	</div>
	@function getImportString
	@param {String} utilName - name of utility or class to be imported
	
*/
jgrouse.getImportString = function(utilName)
{
	var arr = utilName.split('.');
	var last = arr.pop();
	
	if (last == '*')
	{
		var newName = arr.join('.');
		var baseObj = jgrouse.resolveName(newName);
		var result = [];
		jgrouse.forEach(baseObj, function(value, name)
		{
			var res = 'var ' + name + '=' + newName + '.' + name + ';';
			result.push(res);
		});	
		return result.join(' ');
	}	
	else
	{
		return 'var ' + last + '=' + arr.join('.') + '.' + last + ';'
	}
}

/**
    Signature of function that could be used for iterating over object or map properties
    @ifunction objectIteratorCallback
    @param {Object} value - current value
    @param {optional Object} key - current key/name of property 
 */
 
/**
    Signature of function that could be used for iterating over arrays/collections
    @ifunction arrayIteratorCallback
    @param {Object} value - current value
    @param {optional int} index - current index in array/collection
 */

/**
	Execute a function for each element of array or member of structure
	@function forEach
	@paramset Syntax 1 - array based (iteration over array elements)
	@param {Array} arr - array to be iterated
	@param {arrayIteratorCallback} func - function to be called for each array element. 
	@param {optional Boolean} backwards - if iteration should start from the end of the array. 
		Default value is "false". Iteration from the end of the array could be 
		useful when iterator function removes elements from the array
	@paramset Syntax 2 - object based (iteration over object properties)
	@param {Object} obj - source object
	@param {objectIteratorCallback} func - function to be called for each object property.
*/
jgrouse.forEach = function(arr, func, backwards)
{
	try
	{
		if (arr instanceof Array)
		{
			if (backwards)
			{
				for (var i = arr.length - 1; i >= 0; i--)
				{
					func(arr[i], i);
				}
			}
			else
			{
				for (var i = 0; i < arr.length; i++)
				{
					func(arr[i], i);
				}
			}
		}
		else if (typeof arr == 'object')
		{
			var dummy = {};
			for (var i in arr)
			{
				if (arr[i] != dummy[i])
				{
					func(arr[i], i);
				}
			}
		}
	}
	catch (ex)
	{
		if (ex != jgrouse.$break)
		{
			throw ex;
		}
	}
}


/**
	Class implementing jGrouse module - a piece of code that could be loaded dynamically
	@class jgrouse.Module
*/
jgrouse.Module = function(name, requires, moduleImports, body, postInit, classLoader)
{
	//jgrouse.log.info('defining ' + name);
	this._name = name;
	if (typeof requires == 'string')
	{
		this._requires = [requires];
	}
	else
	{
		this._requires = requires;
	}
	
	this._body = body;
	this._classes = [];
	this._classLoader = classLoader;
	if (typeof moduleImports == 'string')
	{
		this._moduleImports = [moduleImports];
	}
	else
	{
		this._moduleImports= moduleImports;
	}
	this._postInit = postInit;
	this._resources = {};
	return this;
}

jgrouse.Module.prototype = 
{
	_requiredLoaded : function()
	{
		if (this._requires)
		{
			for (var i = this._requires.length; i--; i >= 0)
			{
				if (!jgrouse.ClassLoader.instance._isLoaded(this._requires[i]))
				{
					return false;
				}
			}
		}
		return true;
	},
	
	_getModuleImports : function()
	{
		var res = '';
		if (this._moduleImports)
		{
			jgrouse.forEach(this._moduleImports, function(item, index)
			{
				res += jgrouse.getImportString(item);
			});
		}
		return '{' + res + '}';
	},
	
	/**
		Initialize module
		@function initModule
	*/
	initModule : function()
	{	
		jgrouse.log.info('initializing ' + this._name);
		var strImp = this._getModuleImports();
		strImp += ' var thisModule = this;';
		this._body(strImp);
		//jgrouse.log.info('added module ' + this._name);
		for (var i = 0; i < this._classes.length; i++)
		{
			var cd = this._classes[i];
			this._classLoader._defineClass(cd);
		}
		//jgrouse.log.info('added classes in ' + this._name);
		if (this._postInit)
		{
			this._postInit(strImp);
		}
		//jgrouse.log.info('invoked postinit for ' + this._name);
	},
	
	/**
		Add class definition to module
		@function addClassDef
		@param {ClassDef} classDef - class definition
	*/
	addClassDef : function(classDef)
	{
		this._classes.push(classDef);
	},
	
	/**
		Gets string representing imports for classes defined in the same module for given class.
		For example, if module defines three classes foo.Bar, foo.Bob and foo.Boo and the imports are being 
		generated for foo.Boo, then the function would return
		<div class="example">
			var Bar = foo.Bar; var Bob = foo.Bob;
		</div>
		@function getImports
		@param {String} forClass - name of class for which the imports are being generated
		@return string containing import definitions, which could be eval'ed
	*/
	getImports : function(forClass)
	{
		var res = '';
		for (var i = 0; i < this._classes.length; i++)
		{
			var cd = this._classes[i];
			if (cd.className != forClass)
			{
				var cda = cd.className.split('.');
				var importName = cda[cda.length - 1];
				var importString = 'var ' + importName + '=' + cd.className + ';';
				res += importString;
			}
		}
		return res;
	},
	
	/**
		Loads a text resource for given module.
		Once loaded, the resource gets cached in module.
		@function getResource
		@param {String} resourceName
		@return loaded resource
	*/
	getResource : function(resourceName)
	{
		var text = this._resources[resourceName];
		if (typeof text != 'undefined')
		{
			return text;
		}
		text = this._classLoader._getResource(this._name, resourceName);
		this._resources[resourceName] = text;
		return text;
	}
}

/**
	jGrouse ClassLoader is responsible for loading all modules, classes and resources.
	@class jgrouse.ClassLoader
*/
jgrouse.ClassLoader = function()
{
	/**
		Map with all modules. Keys are module names, values are module instances
		@variable {private Object} _allModules
	*/
	this._allModules = {};
	/**
		Map with loaded modules
		@variable {private Object} _loadedModules
	*/
	this._loadedModules = {};
	/**
		Map with modules that have been loaded but not initalized yet
		@variable {private Object} _pendingModules
	*/
	this._pendingModules = {};
	/**
		Map with names of modules that are currently loading
		@variable {private Object} _loadingModules
	*/
	this._loadingModules = {};
	/**
		Mapping of module namespaces to URLs.
		Key is name of namespace, value is base URL for that namespace
		@variable {private Object} _namespaces		
	*/
	this._namespaces = {};
	/**
		Map with callback functions that should be called once certain module is loaded.
		Key is module name, value is array of callback functions for that module
		@variable {private Object} _callbacks
	*/
	this._callbacks = {};
	/**
		Indicator if modules should be loaded with support for debugging (using &ltscript&gt; tag).
		@variable {boolean} _debug
	*/
	this._debug = !!jgrouse_config.debug;
	/**
		Module that is currently being initialized
		@variable {private jgrouse.Module} ?
	*/
	this._currentModule = null;
	/**
		Indicator if loading of modules should be deferred
		@variable {private Boolean} _deferLoading
	*/
	this._deferLoading = false;
	/**
		Indicator if modules should be loaded synchronously. Setting to true is useful for debugging.
		@variable {private Boolean} _syncLoading
	*/
	this._syncLoading = !!jgrouse_config.syncLoading;
	
	/**
	 * Current URL that is being loaded, for async loaders
	 * @variable {private String} _currentURL
	 */
	_currentURL : null;
	
	return this;
}

jgrouse.ClassLoader.prototype = 
{

	/**
		Registers namespace with classloader. 
		For example, call to addNamespaceLocation('mynamespace', 'somepath') instructs classloader to load all classes 
		and resources in mynamespace from given path somepath
		@function addNamespaceLocation
		@param {String} namespace - name of namespace that is being registered
		@param {String} path - path from which modules and resources would be loaded

	*/	
	addNamespaceLocation : function(namespace, path)
	{
		if (namespace[namespace.length - 1] != '.')
		{
			namespace += '.';
		}
		this._namespaces[namespace] = path;
	},
	
	_getNamespacePathForModule : function(moduleName)
	{
		for (var i in this._namespaces)
		{
			if (moduleName.indexOf(i) == 0)
			{
				return this._namespaces[i];
			}
		}
	},
	
	/**
		Set indicator whether the loading and initialization of modules should be deferred.
		If value is set to true, it would force revalidation of all pending modules
		@function setDeferLoading
		@param {boolean} doDefer 
	*/
	setDeferLoading : function(doDefer)
	{
		this._deferLoading = doDefer;
		if (!doDefer)
		{
			this._doInitializeModules();
		}
	},
	
	_doInitializeModules : function()
	{
		// note - this "cyclic" approach is required in order to make it work in Safari that has very shallow stack

		var v = true;
		while (v)
		{
			v = this._checkNotLoadedModules();
		}
	},
	
	_isLoaded : function(name)
	{
		return this._loadedModules[name] != null;
	},
	
	_initModule : function(module)
	{
		var oldModule = this._currentModule;
		this._currentModule = module;
		module.initModule();
		if (this._callbacks[module._name])
		{
			jgrouse.forEach(this._callbacks[module._name], function(callback)
			{
				callback();
			});
			delete this._callbacks[module._name];
		}
		this._currentModule = oldModule;
	},
	
	/**
		Check if module with given name was loaded
		@function {boolean} isLoaded
		@param {String} moduleName
		@returns true if module is loaded and initialized
	*/
	isLoaded : function(moduleName)
	{
		return !!this._loadedModules[moduleName];
	},
	
	_checkModule : function(module)
	{
		if (!this._deferLoading)
		{
			if (module._requiredLoaded())
			{
				this._loadedModules[module._name] = module;
				//jgrouse.log.info("loaded module " + module._name);
				delete this._pendingModules[module._name];
				this._initModule(module);
				//jgrouse.log.info("completed initialization of " + module._name);
				return true;
			}
			else
			{
				this._pendingModules[module._name] = module;
				for (var i = module._requires.length - 1; i >= 0; i--)
				{
					this._startModuleLoading(module._requires[i]);
				}
			}
		}
		else
		{
			this._pendingModules[module._name] = module;
		}
		return false;
	},
	
	_checkNotLoadedModules : function()
	{
		var tmp = [];
		var processed = false;
		for (var i in this._pendingModules)
		{
			tmp.push(this._pendingModules[i]);
		}
		for (var i = tmp.length - 1; i >= 0; i--)
		{
			if (this._pendingModules[tmp[i]._name])
			{
				processed |= this._checkModule(tmp[i]);
			}
		}
		return processed;
	},
	
	_startModuleLoading : function(moduleName)
	{
		if (!(this._allModules[moduleName] || this._loadingModules[moduleName]))
		{
			this._loadingModules[moduleName] = true;
			this._doLoadModule(moduleName);
		}
	},
	
	
	/**
		Read file. If callback function was specified, then loads the text asynchronously and triggers callback.
		@function {String} getText
		@param {String} url
		@param {function(String)} callback - function to be called when the text is loaded
	*/
	getText : function(url, callback)
	{
		var transport = jgrouse.getTransport();
		var async = !!callback;
		transport.open('GET', url, async);
		
		if (async)
		{
			var f = function()
			{
				if (transport.readyState == 4)
				{
					var oldURL = this._currentURL;
					this._currentURL = jgrouse.fullUrl(url);
					try
					{
						jgrouse.log.debug('completed loading in async way ' + url + ' status=' + transport.status);
						transport.onreadystatechange = jgrouse.dummy;
						if (transport.status >= 200 && transport.status < 300)
						{
							callback(transport.responseText);
						}
						else
						{
							callback(transport);
						}
					}
					finally
					{
						this._currentURL = oldURL;
					}
				}
			}
			transport.onreadystatechange = f;
		}
		transport.send("");
		if (!async)
		{
			if (transport.status >= 200 && transport.status < 300)
			{
				return transport.responseText;
			}
			else
			{
				throw transport.statusText;
			}
		}
	},
	
	/**
		Forces framework to load given module. Once module is loaded with all dependable modules and all the loaded modules
		are initialized, the framework would trigger the passed callback
		@function requireModule
		@param {String} moduleName - name of module to load
		@param {optional function()} callback - function that would be triggered after module is loaded and initialized 
	*/
	requireModule : function(moduleName, callback)
	{
		if (!this._allModules[moduleName] || this._pendingModules[moduleName])
		{
			if (callback)
			{
				if (!this._callbacks[moduleName])
				{
					this._callbacks[moduleName] = [callback];					
				}
				else
				{
					this._callbacks[moduleName].push(callback);
				}
			}
			this._startModuleLoading(moduleName);
		}
		else
		{
			callback();
		}
	},
	
	getResourcePath : function(moduleName, resourceName)
	{
		var path = this._getNamespacePathForModule(moduleName);
		if (path)
		{
			var fullPath = null;
			if (resourceName.charAt(0) != '/')
			{
				var arr = moduleName.split('.');
				arr.pop();
				arr = arr.join('/') + '/';
				fullPath = path + arr + resourceName;
			}
			else
			{
				fullPath = path + resourceName.substr(1);
			}
			return fullPath;
		}
		else
		{
			return null;
		}	
	},
	
	_getResource : function(moduleName, resourceName)
	{
		var path = this.getResourcePath(moduleName, resourceName);
		if (path)
		{
			var text = null;
			try
			{	
				text = this.getText(path);
			}
			catch (error)
			{
				throw "Resource " + result + " could not be loaded; Error : " + error;
			}
			return text;
		}
		else
		{
			throw "Cannot find namespace for module " + moduleName;
		}
	},
	
	_doLoadModule : function(moduleName)
	{
	
		var athis = this;
		var path = this._getNamespacePathForModule(moduleName);
	
		function processLoad(text)
		{
			if (typeof text == 'string')
			{
				if (athis._debug)
				{
					var tag = document.createElement('script');
					tag.type = "text/javascript";
					tag.src = result;
					jgrouse._srcTag.parentNode.appendChild(tag);
				}
				else
				{
					eval(text);
				}
			}
			else
			{
				var s = "Cannot load module " + moduleName + " from path " + result + "; Error: " + text.statusText;
				jgrouse.log.error(s);
				throw s;
			}
		
		}
	
		if (path)
		{
			var arr = moduleName.split('.');
			var result = path + arr.join('/') + '.js';
			var text = null;
			if (this._syncLoading)
			{
				try
				{
					text = this.getText(result);
				}
				catch (error)
				{
					throw "Cannot load module " + moduleName + " from path " + result + "; Error: " + error;
				}
				processLoad(text);
			}
			else
			{
				this.getText(result, processLoad)
			}
		}
		else
		{
			throw 'Cannot determine path for ' + moduleName;
		}
	},

	/**
		Adds module to classloader.
		@function addModule
		@param {String} name - name of the module
		@param {String[]} requires - names of required modules
		@param {String[]} imports - names that should be imported into module's namespace
		@param {function(String)} body - function containing module's body. Parameter of that 
			function is a string containing necessary "sugar" functionality that would be eval'ed in module's body namespace
		@param {function(String)} postInit - function that would be called to complete module's initiation. 
			Parameter of that function is a string containing "sugar" functionality that would be eval'ed in module's postInit namespace
	*/	
	addModule : function(name, requires, imports, body, postInit)
	{
		var module = new jgrouse.Module(name, requires, imports, body, postInit, this);
		delete this._loadingModules[name];
		this._allModules[name] = module;
		if (this._checkModule(module))
		{
			this._doInitializeModules();
		}
	},
	
	_defineClass : function(classDef)
	{
		var subclassName = classDef.className;
		var superclassName = classDef.superclassName;
		var properties = classDef.properties;
		
		var subclass = jgrouse.resolveName(subclassName);
		if (!subclass)
		{
			subclass = jgrouse.create(subclassName);
		}
		var superclass = superclassName? jgrouse.resolveName(superclassName) : null;
		if (superclassName && !superclass)
		{
			throw new jgrouse.Exception('Base class ' + superclassName + ' not found');
		}
		if (superclass)
		{
			superclass.prototype._prototyping = true;
			subclass.prototype = new superclass();
			superclass.prototype._prototyping = false;
		}
		var packageArr = subclassName.split('.');
		packageArr.pop();
		var packageName = packageArr.join('.');
		if (packageName.length == 0)
		{
			packageName = 'jgrouse._global';
		}
		var embed = 'var _package = ' + packageName + ';'
				+ 'var _class = ' + subclassName + ';'
				+ 'var _className = \'' + subclassName + '\';';
	
		if (superclass)
		{
			embed += 
			 'var _super = function(obj, method, args) {'
			+ ' var arr = []; for (var i = 2; i < arguments.length; i++) {arr.push(arguments[i])};' 
			+ ' return ' + superclassName + '.prototype[method].apply(obj, arr) };';
			
			embed += 'var _sup = function(obj, args) {'
			 + '	var member = args.callee.__$memberName; '
			 + '	return ' + superclassName + '.prototype[member].apply(obj, args);};';
		}
		
		if (jgrouse_config.nologs)
		{
			embed += ['function l_log(level, msg) {} ',
					  'function l_error(msg){}',
					  'function l_warn(msg){}',
					  'function l_info(msg){}',
					  'function l_debug(msg){}'].join(' ');
		}
		else
		{
			embed += ['function l_log(level, msg) {jgrouse.log.log(level, msg, _className)};',
			          'function l_error(msg){l_log(jgrouse.log.ERROR, msg)};',
			          'function l_warn(msg){l_log(jgrouse.log.WARN, msg)};',
			          'function l_info(msg){l_log(jgrouse.log.INFO, msg)};',
			          'function l_debug(msg){l_log(jgrouse.log.DEBUG, msg)};'].join(' ');
		}
		
		if (this._currentModule)
		{
			embed += this._currentModule.getImports(subclassName);
		}
		embed = '{' + embed + '}';
		subclass.prototype.RTTI = subclassName;
		subclass.prototype.superclass = superclass;
		var props = (typeof properties == 'function' || properties instanceof Function)? 
			properties(embed) : properties;
		for (var p in props)
		{
			var f = props[p];
			subclass.prototype[p] = f;
			if (typeof f == 'function' && f instanceof Function)
			{
				f.__$memberName = p;
			}
		}
		return subclass;
	},
	
	/**
		Define class.
		@function define
		@param {String} subclassName - name of class to be defined
		@param {String} superclassName - name of superclass or null if the class does not have superclass
		@param {function(salt)} properties - function that returns members of class. Parameter contains necessary 
			functionality that should be eval'ed in the namespace of properties
		@see jgrouse.define
	*/
	define : function( subclassName, superclassName, properties)
	{
		var classdef = {className : subclassName, superclassName : superclassName, properties : properties};
		if (this._currentModule)
		{
			this._currentModule.addClassDef(classdef);
		}
		else
		{
			this._defineClass(classdef);
		}
	}	
}

jgrouse.ClassLoader._initHistoryIFrame = function()
{
	if (!jgrouse_config.nohistory)
	{
		var historyFile = jgrouse.ClassLoader.instance.getResourcePath('jgrouse.browser.HistoryTracker', 'history.html');
        var hash=window.location.hash;
        if (hash && hash != '#')
        {
            hash = escape(hash.substring(1));
            historyFile += '?' + hash;
        }		
		document.writeln("<iframe id='_jgrouse_history_tracker' src='" + historyFile + "' " +
				"style='position:absolute; top:-1000px; left:-1000px'></iframe>");
	}
}

jgrouse.ClassLoader._init = function()
{
	if (!jgrouse._srcTag)
	{
		var data = document.getElementsByTagName("script");
		for (var i = data.length - 1; i >= 0; i--)
		{
			var s = data[i];
			if (s.src && s.src.indexOf(jgrouse._startupFile) >= 0)
			{
				jgrouse._srcTag = s;
				break;
			}
		}
		var base = s.src.substring(0, s.src.length - jgrouse._startupFile.length);
		jgrouse.ClassLoader.instance = new jgrouse.ClassLoader();
		jgrouse.ClassLoader.instance.addNamespaceLocation('jgrouse', base);
		
		jgrouse.ClassLoader._initHistoryIFrame();
	}
}


jgrouse.addUnloadHook(function()
{
	jgrouse._srcTag = null;
});


jgrouse.ClassLoader._init();